package kubernetes

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"

	"github.com/golang/glog"
	"github.com/gorilla/mux"
	apps "k8s.io/api/apps/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	authapi "eccgateway/pkg/api/auth"
	"eccgateway/pkg/util"
)

type DeployController struct{}

func (c DeployController) ListDeploy(w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}

	q := r.URL.Query()
	labelSelectors := parseLabelSelector(q.Get(labelSelector))

	delete(labelSelectors, groupIDKey)
	delete(labelSelectors, userIDKey)
	largestRole := authapi.GetLargestRolePermission(info.Role)
	switch largestRole {
	case authapi.OpServiceRole:
		break
	case authapi.AdminRole:
		labelSelectors[groupIDKey] = fmt.Sprintf("%v", info.Group.ID)
	case authapi.UserRole:
		labelSelectors[userIDKey] = fmt.Sprintf("%v", info.UserID)
	default:
		labelSelectors[userIDKey] = fmt.Sprintf("%v", info.UserID)
	}

	if len(labelSelectors) != 0 {
		q.Del(labelSelector)
		q.Add(labelSelector, constructLabelSelector(labelSelectors))
		r.URL.RawQuery = q.Encode()
	}

	(&KubernetesController{}).ServeHTTP(w, r)
}

func (c DeployController) CreateDeploy(w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}

	requestBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		glog.Errorf("request body read failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("request body read failed, %v", err))
		return
	}

	deploy := apps.Deployment{}
	err = json.Unmarshal(requestBody, &deploy)
	if err != nil {
		glog.Errorf("unmarshal request body failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("unmarshal request body failed, %v", err))
		return
	}

	clusterName := ""
	if deploy.Labels != nil {
		clusterName = deploy.Labels[clusterNameKey]
	}
	if clusterName == "" {
		glog.Errorf("create deploy failed, the deploy does not specify which cluster it belongs to, user: %v/%v, deploy name: %v", info.Name, info.UserID, deploy.Name)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, "create deploy failed, the deploy does not specify which cluster it belongs to")
		return
	}
	cluster, err := util.GetCluster(clusterName)
	if err == util.NotFoundError {
		glog.Errorf("get cluster from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusNotFound, err.Error())
		return
	}
	if err != nil {
		glog.Errorf("get cluster from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, err.Error())
		return
	}

	namespaceList, err := util.GetNamespaceListByCluster(clusterName)
	if err == util.NotFoundError {
		glog.Errorf("get namespace list from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusNotFound, err.Error())
		return
	}
	if err != nil {
		glog.Errorf("get namespace list from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, err.Error())
		return
	}

	isInCluster := false
	namespace := mux.Vars(r)["namespace"]
	for _, ns := range namespaceList.Items {
		if ns.Name == namespace {
			isInCluster = true
			break
		}
	}
	if !isInCluster {
		errorMessage := fmt.Sprintf("no permission to create deployment in namespace %v", namespace)
		glog.Errorf("%v, request user: %v/%v, user group: %v cluster: %v, cluster group: %v", errorMessage,
			info.Name, info.UserID, info.Group.ID,
			clusterName, cluster.Labels[groupIDKey])
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
		return
	}

	largestRole := authapi.GetLargestRolePermission(info.Role)
	if !(cluster.Labels[groupIDKey] == strconv.Itoa(info.Group.ID) || largestRole == authapi.OpServiceRole ){
		errorMessage := fmt.Sprintf("no permission to create deployment in cluster %v", clusterName)
		glog.Errorf("%v, request user: %v/%v, user group: %v, user role: %v, cluster: %v, cluster group: %v", errorMessage,
			info.Name, info.UserID, info.Group.ID, largestRole,
			clusterName, cluster.Labels[groupIDKey])
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
		return
	}

	deploy.Namespace = namespace
	if deploy.Annotations == nil {
		deploy.Annotations = make(map[string]string)
	}
	deploy.Annotations[creatorAnnotationKey] = fmt.Sprintf("%v", info.Name)
	if deploy.Labels == nil {
		deploy.Labels = make(map[string]string)
	}
	deploy.Labels[appKey] = deploy.Name
	deploy.Labels[userIDKey] = fmt.Sprintf("%v", info.UserID)
	deploy.Labels[userNameKey] = fmt.Sprintf("%v", info.Name)
	deploy.Labels[groupIDKey] = cluster.Labels[groupIDKey]

	if deploy.Spec.Selector.MatchLabels == nil {
		deploy.Spec.Selector.MatchLabels = make(map[string]string)
	}
	deploy.Spec.Selector.MatchLabels[appKey] = deploy.Name
	deploy.Spec.Selector.MatchLabels[clusterNameKey] = clusterName
	deploy.Spec.Selector.MatchLabels[userIDKey] = fmt.Sprintf("%v", info.UserID)
	deploy.Spec.Selector.MatchLabels[userNameKey] = fmt.Sprintf("%v", info.Name)
	deploy.Spec.Selector.MatchLabels[groupIDKey] = fmt.Sprintf("%v", info.Group.ID)

	if deploy.Spec.Template.Labels == nil {
		deploy.Spec.Template.Labels = make(map[string]string)
	}
	deploy.Spec.Template.Labels[appKey] = deploy.Name
	deploy.Spec.Template.Labels[clusterNameKey] = clusterName
	deploy.Spec.Template.Labels[userIDKey] = fmt.Sprintf("%v", info.UserID)
	deploy.Spec.Template.Labels[userNameKey] = fmt.Sprintf("%v", info.Name)
	deploy.Spec.Template.Labels[groupIDKey] = fmt.Sprintf("%v", info.Group.ID)

	if deploy.Spec.Template.Spec.NodeSelector == nil {
		deploy.Spec.Template.Spec.NodeSelector = make(map[string]string)
	}
	deploy.Spec.Template.Spec.NodeSelector[clusterNameKey] = clusterName

	newRequestBody, _ := json.Marshal(deploy)

	req, err := http.NewRequest(r.Method, r.URL.String(), bytes.NewBuffer(newRequestBody))
	if err != nil {
		glog.Errorf("construct request failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("construct request failed, %v", err))
		return
	}
	req.RequestURI = r.RequestURI

	(&KubernetesController{}).ServeHTTP(w, req)
}

func (c DeployController) getDeploy(name, namespace string) (*apps.Deployment, error) {
	client := util.GetKubernetesClient()

	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/apis/apps/v1/namespaces/%s/deployments/%s", kubernetesAddress, namespace, name), nil)
	if err != nil {
		glog.Errorf("construct request to get deploy failed, err: %v", err)
		return nil, err
	}

	resp, err := client.Do(req)
	if err != nil {
		glog.Errorf("send request to get deploy failed, err: %v", err)
		return nil, err
	}

	if resp.StatusCode == http.StatusNotFound {
		return nil, util.NotFoundError
	}

	requestBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		glog.Errorf("request body read failed, %v", err)
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		status := metav1.Status{}
		err = json.Unmarshal(requestBody, &status)
		if err != nil {
			glog.Errorf("unmarshal response body failed, deploy name: %v/%v, err: %v", namespace, name, err)
			return nil, fmt.Errorf("get deploy failed")
		}
		return nil, fmt.Errorf("%v", status.Message)
	}

	deploy := apps.Deployment{}
	err = json.Unmarshal(requestBody, &deploy)
	if err != nil {
		glog.Errorf("unmarshal response body failed, deploy name: %v/%v, err: %v", namespace, name, err)
		return nil, fmt.Errorf("get deploy failed")
	}

	return &deploy, nil
}

func (c DeployController) GetDeploy(w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	largestRole := authapi.GetLargestRolePermission(info.Role)

	deployName := mux.Vars(r)["name"]
	namespace := mux.Vars(r)["namespace"]
	deploy, err := c.getDeploy(deployName, namespace)
	if err == util.NotFoundError {
		glog.Errorf("get deploy from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusNotFound, err.Error())
		return
	}
	if err != nil {
		glog.Errorf("get deploy from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, err.Error())
		return
	}

	if !(largestRole == authapi.OpServiceRole ||
		(largestRole == authapi.AdminRole && deploy.Labels[groupIDKey] == strconv.Itoa(info.Group.ID)) ||
		(largestRole == authapi.UserRole && deploy.Labels[userIDKey] == info.UserID)) {
		errorMessage := fmt.Sprintf("no permission to get deploy %v/%v", namespace, deployName)
		glog.Errorf("%v, deploy group: %v, creator: %v/%v, request user: %v/%v, user group: %v, user role: %v", errorMessage,
			deploy.Labels[groupIDKey], deploy.Labels[userNameKey], deploy.Labels[userIDKey],
			info.Name, info.UserID, info.Group.ID, largestRole)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
		return
	}

	respBody, _ := json.Marshal(deploy)
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(respBody)
}

func (c DeployController) DeleteDeploy(w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	largestRole := authapi.GetLargestRolePermission(info.Role)

	deployName := mux.Vars(r)["name"]
	namespace := mux.Vars(r)["namespace"]
	if largestRole == authapi.OpServiceRole {
		glog.Infof("start delete deploy, deploy name: %v/%v, user: %v/%v, user role: %v", namespace, deployName, info.Name, info.UserID, largestRole)
		(&KubernetesController{}).ServeHTTP(w, r)
		return
	}

	deploy, err := c.getDeploy(deployName, namespace)
	if err == util.NotFoundError {
		glog.Errorf("get deploy from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusNotFound, err.Error())
		return
	}
	if err != nil {
		glog.Errorf("get deploy from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, err.Error())
		return
	}
	if (largestRole == authapi.AdminRole && deploy.Labels[groupIDKey] == strconv.Itoa(info.Group.ID)) ||
		(largestRole == authapi.UserRole && deploy.Labels[userIDKey] == info.UserID) {
		glog.Infof("start delete deploy, deploy name: %v/%v, user: %v/%v, user role: %v", namespace, deployName, info.Name, info.UserID, largestRole)
		(&KubernetesController{}).ServeHTTP(w, r)
		return
	}

	errorMessage := fmt.Sprintf("no permission to delete deploy %v/%v", namespace, deployName)
	glog.Errorf("%v, deploy group: %v, creator: %v/%v, request user: %v/%v, user group: %v, user role: %v", errorMessage,
		deploy.Labels[groupIDKey], deploy.Labels[userNameKey], deploy.Labels[userIDKey],
		info.Name, info.UserID, info.Group.ID, largestRole)
	util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
	return
}

func (c DeployController) UpdateDeploy(w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	largestRole := authapi.GetLargestRolePermission(info.Role)

	deployName := mux.Vars(r)["name"]
	namespace := mux.Vars(r)["namespace"]
	var oldDeploy *apps.Deployment
	oldDeploy, err = c.getDeploy(deployName, namespace)
	if err == util.NotFoundError {
		glog.Errorf("get deploy from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusNotFound, err.Error())
		return
	}
	if err != nil {
		glog.Errorf("get deploy from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, err.Error())
		return
	}
	if !(largestRole == authapi.OpServiceRole ||
		(largestRole == authapi.AdminRole && oldDeploy.Labels[groupIDKey] == strconv.Itoa(info.Group.ID)) ||
		(largestRole == authapi.UserRole && oldDeploy.Labels[userIDKey] == info.UserID)) {
		errorMessage := fmt.Sprintf("no permission to update deploy %v/%v", namespace, deployName)
		glog.Errorf("%v, deploy group: %v, creator: %v/%v, request user: %v/%v, user group: %v, user role: %v", errorMessage,
			oldDeploy.Labels[groupIDKey], oldDeploy.Labels[userNameKey], oldDeploy.Labels[userIDKey],
			info.Name, info.UserID, info.Group.ID, largestRole)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
		return
	}

	requestBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		glog.Errorf("request body read failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("request body read failed, %v", err))
		return
	}

	deploy := apps.Deployment{}
	err = json.Unmarshal(requestBody, &deploy)
	if err != nil {
		glog.Errorf("unmarshal request body failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("unmarshal request body failed, %v", err))
		return
	}

	if deploy.Labels == nil {
		deploy.Labels = make(map[string]string)
	}
	deploy.Labels[appKey] = deploy.Name
	deploy.Labels[clusterNameKey] = oldDeploy.Labels[clusterNameKey]
	deploy.Labels[userIDKey] = oldDeploy.Labels[userIDKey]
	deploy.Labels[userNameKey] = oldDeploy.Labels[userNameKey]
	deploy.Labels[groupIDKey] = oldDeploy.Labels[groupIDKey]

	if deploy.Spec.Selector == nil {
		deploy.Spec.Selector = &metav1.LabelSelector{}
	}
	if deploy.Spec.Selector.MatchLabels == nil {
		deploy.Spec.Selector.MatchLabels = make(map[string]string)
	}
	deploy.Spec.Selector.MatchLabels[appKey] = oldDeploy.Spec.Selector.MatchLabels[appKey]
	deploy.Spec.Selector.MatchLabels[clusterNameKey] = oldDeploy.Spec.Selector.MatchLabels[clusterNameKey]
	deploy.Spec.Selector.MatchLabels[userIDKey] = oldDeploy.Spec.Selector.MatchLabels[userIDKey]
	deploy.Spec.Selector.MatchLabels[userNameKey] = oldDeploy.Spec.Selector.MatchLabels[userNameKey]
	deploy.Spec.Selector.MatchLabels[groupIDKey] = oldDeploy.Spec.Selector.MatchLabels[groupIDKey]

	if deploy.Spec.Template.Labels == nil {
		deploy.Spec.Template.Labels = make(map[string]string)
	}
	deploy.Spec.Template.Labels[appKey] = deploy.Name
	deploy.Spec.Template.Labels[clusterNameKey] = oldDeploy.Spec.Template.Labels[clusterNameKey]
	deploy.Spec.Template.Labels[userIDKey] = oldDeploy.Spec.Template.Labels[userIDKey]
	deploy.Spec.Template.Labels[userNameKey] = oldDeploy.Spec.Template.Labels[userNameKey]
	deploy.Spec.Template.Labels[groupIDKey] = oldDeploy.Spec.Template.Labels[groupIDKey]

	if len(deploy.Spec.Template.Spec.Containers) == 0 {
		deploy.Spec.Template.Spec.Containers = oldDeploy.Spec.Template.Spec.Containers
	}

	newRequestBody, _ := json.Marshal(deploy)

	req, err := http.NewRequest(r.Method, r.URL.String(), bytes.NewBuffer(newRequestBody))
	if err != nil {
		glog.Errorf("construct request failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("construct request failed, %v", err))
		return
	}
	req.RequestURI = r.RequestURI
	req.Header.Set("Content-Type", "application/merge-patch+json")

	glog.Infof("start update deploy, deploy name: %v, user: %v/%v, user role: %v", deployName, info.Name, info.UserID, largestRole)
	(&KubernetesController{}).ServeHTTP(w, req)
	return
}
